package com.monochromebytes.mononet.virtual;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import com.monochromebytes.mononet.packet.UDPSocket;
import com.monochromebytes.mononet.packet.UDPSocketListener;

public class ConnectionSocket implements UDPSocketListener {

    private final HashMap<VirtualAddress, Connection>   connections;
    private final ArrayList<ConnectionSocketListener>   socketListeners;
    private final UDPSocket                             socket;
    private final ForwardingInbox                       inbox;
    private final QueuedOutbox                          outbox;
    private final InetSocketAddress                     localAddress;
    private final ConnectionArbiter                     arbiter;

    public ConnectionSocket(InetSocketAddress localAddress, ConnectionArbiter arbiter) {
        this.connections = new HashMap<VirtualAddress, Connection>();
        this.socketListeners = new ArrayList<ConnectionSocketListener>();
        this.inbox = new ForwardingInbox(this);
        this.outbox = new QueuedOutbox();
        this.socket = new UDPSocket(this.inbox, this.outbox);
        this.localAddress = localAddress;
        this.arbiter = arbiter;
    }

    public void open() throws IOException {
        this.socket.open(this.localAddress);
    }

    public boolean isOpen() {
        return this.socket.isOpen();
    }

    public void close() throws IOException {
        this.socket.close();
    }

    public Connection getConnection(VirtualAddress address) {
        Connection connection = this.connections.get(address);
        if (connection == null) {
            if (this.arbiter.accept(address)) {
                connection = new Connection(this, address);
                synchronized (this.connections) {
                    this.connections.put(address, connection);
                }

                synchronized (this.socketListeners) {
                    for (ConnectionSocketListener listener : this.socketListeners) {
                        listener.connected(this, connection);
                    }
                }
            }
        }
        return connection;
    }

    public QueuedOutbox getOutbox() {
        return this.outbox;
    }

    public void addSocketListener(ConnectionSocketListener listener) {
        synchronized (this.socketListeners) {
            this.socketListeners.add(listener);
        }
    }

    public void removeSocketListener(ConnectionSocketListener listener) {
        synchronized (this.socketListeners) {
            this.socketListeners.remove(listener);
        }
    }

    public void getAllConnections(List<Connection> connections) {
        synchronized (this.connections) {
            connections.addAll(this.connections.values());
        }
    }

    public void removeConnection(Connection connection) {
        synchronized (this.connections) {
            this.connections.remove(connection.getAddress());
        }
    }

    public InetSocketAddress getLocalAddress() {
        return this.localAddress;
    }

    @Override
    public void socketClosed(UDPSocket socket, IOException e) {
        synchronized (this.socketListeners) {
            for (ConnectionSocketListener listener : this.socketListeners) {
                listener.socketClosed(this, e);
            }
        }
    }

    @Override
    public void socketOpened(UDPSocket socket) {
        synchronized (this.socketListeners) {
            for (ConnectionSocketListener listener : this.socketListeners) {
                listener.socketOpened(this);
            }
        }
    }
}
